package oneD.t3dviewer2;

/* matrix3d.java

   History:

   12 Jul 2006  Use tuple3d in representation.  Define transform() for
                arrays of tuple3ds.
   29 May 2006  Remove set().  Add arot().  Four assigns
                to a line is clearer in a lot of places.  Put "m_" prefix
                on all member variables.  Use Math.PI instead of pi.
                Add more comments.  Don't bother with intermediate "l"
                variables in transform().
   26 May 2006  Add set() and supporting constants.
 */

/**
 * Adapted from the JDK 1.1.1 wireframe example where it is characterized as "a
 * fairly conventional 3D matrix object that can transform sets of 3D points and
 * perform a variety of manipulations on the transform." With the arrangement of
 * the matrix shown at the beginning of the class, the transform can be thought
 * of as a matrix multiplication being applied to a 3D column vector (augmented
 * by 1.0 in the fourth position) on the right. All the manipulations below can
 * be thought of as post-transformation where the resulting transform is as if
 * the additional transformation is applied *after* the existing one is applied
 * to the column vector. The original documentation referred to this as
 * right-hand side, but I find it clearer to call it left-hand side, so I'm not
 * going to mention sides at all.
 */
public class matrix3d {
	tuple3d m_x, m_y, m_z;
	float m_xo, m_yo, m_zo;

	/** Create a new unit matrix */
	public matrix3d() {
		m_x = new tuple3d(1.0f, 0.0f, 0.0f);
		m_y = new tuple3d(0.0f, 1.0f, 0.0f);
		m_z = new tuple3d(0.0f, 0.0f, 1.0f);
	}

	/** Scale by f in all dimensions */
	public void scale(float f) {
		m_x.scale(f);
		m_xo *= f;
		m_y.scale(f);
		m_yo *= f;
		m_z.scale(f);
		m_zo *= f;
	}

	/**
	 * Scale along each axis independently -- scaling done *after* the original
	 * transform is applied.
	 */
	public void scale(float xf, float yf, float zf) {
		m_x.scale(xf);
		m_xo *= xf;
		m_y.scale(yf);
		m_yo *= yf;
		m_z.scale(zf);
		m_zo *= zf;
	}

	/** Translate the origin */
	public void translate(float x, float y, float z) {
		m_xo += x;
		m_yo += y;
		m_zo += z;
	}

	/**
	 * rotate theta degrees about the x axis -- rotation done *after* the
	 * original transform is applied.
	 */
	public void xrot(double theta) {
		// convert angle to radians
		theta *= (Math.PI / 180);

		// precompute sin and cos
		double ct = Math.cos(theta);
		double st = Math.sin(theta);

		float Nyx = (float) (m_y.x() * ct + m_z.x() * st);
		float Nyy = (float) (m_y.y() * ct + m_z.y() * st);
		float Nyz = (float) (m_y.z() * ct + m_z.z() * st);
		float Nyo = (float) (m_yo * ct + m_zo * st);

		float Nzx = (float) (m_z.x() * ct - m_y.x() * st);
		float Nzy = (float) (m_z.y() * ct - m_y.y() * st);
		float Nzz = (float) (m_z.z() * ct - m_y.z() * st);
		float Nzo = (float) (m_zo * ct - m_yo * st);

		m_y.set(Nyx, Nyy, Nyz);
		m_yo = Nyo;
		m_z.set(Nzx, Nzy, Nzz);
		m_zo = Nzo;
	}

	/**
	 * rotate theta degrees about the y axis -- rotation done *after* the
	 * original transform is applied.
	 */
	public void yrot(double theta) {
		// convert angle to radians
		theta *= (Math.PI / 180);

		// precompute sin and cos
		double ct = Math.cos(theta);
		double st = Math.sin(theta);

		float Nxx = (float) (m_x.x() * ct + m_z.x() * st);
		float Nxy = (float) (m_x.y() * ct + m_z.y() * st);
		float Nxz = (float) (m_x.z() * ct + m_z.z() * st);
		float Nxo = (float) (m_xo * ct + m_zo * st);

		float Nzx = (float) (m_z.x() * ct - m_x.x() * st);
		float Nzy = (float) (m_z.y() * ct - m_x.y() * st);
		float Nzz = (float) (m_z.z() * ct - m_x.z() * st);
		float Nzo = (float) (m_zo * ct - m_xo * st);

		m_x.set(Nxx, Nxy, Nxz);
		m_xo = Nxo;
		m_z.set(Nzx, Nzy, Nzz);
		m_zo = Nzo;
	}

	/**
	 * rotate theta degrees about the z axis -- rotation done *after* the
	 * original transform is applied.
	 */
	public void zrot(double theta) {
		// convert angle to radians
		theta *= (Math.PI / 180);

		// precompute sin and cos
		double ct = Math.cos(theta);
		double st = Math.sin(theta);

		float Nxx = (float) (m_x.x() * ct - m_y.x() * st);
		float Nxy = (float) (m_x.y() * ct - m_y.y() * st);
		float Nxz = (float) (m_x.z() * ct - m_y.z() * st);
		float Nxo = (float) (m_xo * ct - m_yo * st);

		float Nyx = (float) (m_y.x() * ct + m_x.x() * st);
		float Nyy = (float) (m_y.y() * ct + m_x.y() * st);
		float Nyz = (float) (m_y.z() * ct + m_x.z() * st);
		float Nyo = (float) (m_yo * ct + m_xo * st);

		m_x.set(Nxx, Nxy, Nxz);
		m_xo = Nxo;
		m_y.set(Nyx, Nyy, Nyz);
		m_yo = Nyo;
	}

	/**
	 * rotate theta degrees about an arbitrary axis with coordinates (x, y, z)
	 * -- rotation done *after* the original transform is applied.
	 */
	public void arot(double theta, float x, float y, float z) {
		// convert angle to radians
		theta *= (Math.PI / 180);

		// normalize axis
		float length = (float) Math.sqrt(x * x + y * y + z * z);
		if (length == 0.0)
			return;
		x /= length;
		y /= length;
		z /= length;

		// precompute sin and cos, x*x, y*y, z*z, x*y, x*z, y*z
		double ct = Math.cos(theta);
		double st = Math.sin(theta);
		float x2 = x * x;
		float y2 = y * y;
		float z2 = z * z;
		float xtimesy = x * y;
		float xtimesz = x * z;
		float ytimesz = y * z;

		// compute transform (D.F. Rogers and J.A. Adams, Mathematical
		// Elements for Computer Graphics, 1976, Chapter 3)
		float Axx = (float) (x2 + (1.0f - x2) * ct);
		float Axy = (float) (xtimesy * (1.0f - ct) - z * st);
		float Axz = (float) (xtimesz * (1.0f - ct) + y * st);
		float Ayx = (float) (xtimesy * (1.0f - ct) + z * st);
		float Ayy = (float) (y2 + (1.0f - y2) * ct);
		float Ayz = (float) (ytimesz * (1.0f - ct) - x * st);
		float Azx = (float) (xtimesz * (1.0f - ct) - y * st);
		float Azy = (float) (ytimesz * (1.0f - ct) + x * st);
		float Azz = (float) (z2 + (1.0f - z2) * ct);

		// apply the transform (see mult below)
		float Nxx = Axx * m_x.x() + Axy * m_y.x() + Axz * m_z.x();
		float Nxy = Axx * m_x.y() + Axy * m_y.y() + Axz * m_z.y();
		float Nxz = Axx * m_x.z() + Axy * m_y.z() + Axz * m_z.z();
		float Nxo = Axx * m_xo + Axy * m_yo + Axz * m_zo;

		float Nyx = Ayx * m_x.x() + Ayy * m_y.x() + Ayz * m_z.x();
		float Nyy = Ayx * m_x.y() + Ayy * m_y.y() + Ayz * m_z.y();
		float Nyz = Ayx * m_x.z() + Ayy * m_y.z() + Ayz * m_z.z();
		float Nyo = Ayx * m_xo + Ayy * m_yo + Ayz * m_zo;

		float Nzx = Azx * m_x.x() + Azy * m_y.x() + Azz * m_z.x();
		float Nzy = Azx * m_x.y() + Azy * m_y.y() + Azz * m_z.y();
		float Nzz = Azx * m_x.z() + Azy * m_y.z() + Azz * m_z.z();
		float Nzo = Azx * m_xo + Azy * m_yo + Azz * m_zo;

		m_x.set(Nxx, Nxy, Nxz);
		m_xo = Nxo;
		m_y.set(Nyx, Nyy, Nyz);
		m_yo = Nyo;
		m_z.set(Nzx, Nzy, Nzz);
		m_zo = Nzo;
	}

	/**
	 * Apply a post transformation -- the resulting transform is as if the new
	 * one were applied *after* the existing one.
	 */
	public void mult(matrix3d m) {
		float Nxx = m.m_x.x() * m_x.x() + m.m_x.y() * m_y.x() + m.m_x.z()
				* m_z.x();
		float Nxy = m.m_x.x() * m_x.y() + m.m_x.y() * m_y.y() + m.m_x.z()
				* m_z.y();
		float Nxz = m.m_x.x() * m_x.z() + m.m_x.y() * m_y.z() + m.m_x.z()
				* m_z.z();
		float Nxo = m.m_x.x() * m_xo + m.m_x.y() * m_yo + m.m_x.z() * m_zo
				+ m.m_xo;

		float Nyx = m.m_y.x() * m_x.x() + m.m_y.y() * m_y.x() + m.m_y.z()
				* m_z.x();
		float Nyy = m.m_y.x() * m_x.y() + m.m_y.y() * m_y.y() + m.m_y.z()
				* m_z.y();
		float Nyz = m.m_y.x() * m_x.z() + m.m_y.y() * m_y.z() + m.m_y.z()
				* m_z.z();
		float Nyo = m.m_y.x() * m_xo + m.m_y.y() * m_yo + m.m_y.z() * m_zo
				+ m.m_yo;

		float Nzx = m.m_z.x() * m_x.x() + m.m_z.y() * m_y.x() + m.m_z.z()
				* m_z.x();
		float Nzy = m.m_z.x() * m_x.y() + m.m_z.y() * m_y.y() + m.m_z.z()
				* m_z.y();
		float Nzz = m.m_z.x() * m_x.z() + m.m_z.y() * m_y.z() + m.m_z.z()
				* m_z.z();
		float Nzo = m.m_z.x() * m_xo + m.m_z.y() * m_yo + m.m_z.z() * m_zo
				+ m.m_zo;

		m_x.set(Nxx, Nxy, Nxz);
		m_xo = Nxo;
		m_y.set(Nyx, Nyy, Nyz);
		m_yo = Nyo;
		m_z.set(Nzx, Nzy, Nzz);
		m_zo = Nzo;
	}

	/** Reinitialize to the unit matrix */
	public void unit() {
		m_x.set(1.0f, 0.0f, 0.0f);
		m_xo = 0.0f;
		m_y.set(0.0f, 1.0f, 0.0f);
		m_yo = 0.0f;
		m_z.set(0.0f, 0.0f, 1.0f);
		m_zo = 0.0f;
	}

	/**
	 * Transform nvert points from v into tv. v contains the input coordinates
	 * in floating point. Three successive entries in the array constitute a
	 * point. tv ends up holding the transformed points as integers; three
	 * successive entries per point. (1, 0, 0) gets mapped to (m_x.x() + m_xo,
	 * m_y.x() + m_yo, m_z.x() + m_zo), (0, 1, 0) to (m_x.y() + m_xo, m_y.y() +
	 * m_yo, m_z.y() + m_zo) and (0, 0, 1) to (m_x.z() + m_xo, m_y.z() + m_yo,
	 * m_z.z() + m_zo).
	 */
	public void transform(float v[], int tv[], int nvert) {
		for (int i = nvert * 3; (i -= 3) >= 0;) {
			float x = v[i];
			float y = v[i + 1];
			float z = v[i + 2];
			tv[i] = (int) (x * m_x.x() + y * m_x.y() + z * m_x.z() + m_xo);
			tv[i + 1] = (int) (x * m_y.x() + y * m_y.y() + z * m_y.z() + m_yo);
			tv[i + 2] = (int) (x * m_z.x() + y * m_z.y() + z * m_z.z() + m_zo);
		}
	}

	/** Transform nvert tuple3ds from v into tv. */
	public void transform(tuple3d v[], tuple3d tv[], int nvert) {
		for (int i = 0; i < nvert; i++) {
			tv[i].set(v[i].dot(m_x) + m_xo, v[i].dot(m_y) + m_yo, v[i].dot(m_z)
					+ m_zo);
		}
	}

	/** Generate a String representation of this matrix3d. */
	public String toString() {
		return "[" + m_x.x() + "," + m_x.y() + "," + m_x.z() + "," + m_xo + ";"
				+ m_y.x() + "," + m_y.y() + "," + m_y.z() + "," + m_yo + ";"
				+ m_z.x() + "," + m_z.y() + "," + m_z.z() + "," + m_zo + "]";
	}
}
